
/**
 * AppleIIGo
 * GCR 3.5" Disk Image class
 * (C) 2008 by Marc S. Ressl (mressl@umich.edu)
 * Released under the GPL
 */

#include "GCRDisk35DiskImage.h"
#include "FDIDiskImage.h"

bool GCRDisk35DiskImage::isDiskImageValid(UINT32 diskType,
										  UINT32 diskImageByteNum) {
	if (diskType != FLOPPYDISKTYPE_3_5)
		return false;
	
	return (diskImageByteNum == GCRDISK35_DISK_BYTENUM);
}

GCRDisk35DiskImage::GCRDisk35DiskImage(char * diskImagePath,
									  UINT32 diskImageOffset,
									  UINT32 diskImageByteNum,
									  bool isWriteProtectionForced) :
GCRDiskImage(FLOPPYDISKTYPE_3_5, diskImagePath, GCRTYPE_6_2) {
	if (!isDiskImageOpened())
		return;
	
	this->diskImageOffset = diskImageOffset;
	
	if (!allocateDisk())
		return;
	
	if (!readDiskImage(diskImageOffset, GCRDISK35_DISK_TRACKNUM))
		return;
	
	setWriteProtected(isWriteProtected() || isWriteProtectionForced);
}

GCRDisk35DiskImage::~GCRDisk35DiskImage() {
	if (!isDiskImageOpened())
		return;
	
	if (!isModified())
		return;
	
	if (!writeDiskImage(diskImageOffset, false))
		FDIDiskImage::writeDiskImage(this);
	else
		writeDiskImage(diskImageOffset, true);
}

UINT32 GCRDisk35DiskImage::getSectorNum(UINT32 diskImageTrackIndex) {
	return (12 - (diskImageTrackIndex / 16 / 2));
}

void GCRDisk35DiskImage::getDiskTrack(UINT32 * trackIndex, UINT32 * headIndex,
									  UINT32 * diskImageTrackByteNum,
									  UINT32 diskImageTrackIndex) {
	*trackIndex = diskImageTrackIndex / DISK_3_5_HEADNUM;
	*headIndex = diskImageTrackIndex % DISK_3_5_HEADNUM;
	*diskImageTrackByteNum = (GCRDISK35_SECTOR_BYTENUM *
							  getSectorNum(diskImageTrackIndex));
}

void GCRDisk35DiskImage::getDiskImageTrack(UINT32 * diskImageTrackIndex,
										   UINT32 * diskImageTrackByteNum,
										   UINT32 trackIndex,
										   UINT32 headIndex) {
	*diskImageTrackIndex = trackIndex / 2 + headIndex;
	if (trackIndex & 0x1)
		*diskImageTrackByteNum = 0;
	else
		*diskImageTrackByteNum = (GCRDISK35_SECTOR_BYTENUM *
								  getSectorNum(*diskImageTrackIndex));
}

void GCRDisk35DiskImage::initGcrHash() {
	gcrHash[0] = 0;
	gcrHash[1] = 0;
	gcrHash[2] = 0;
}

void GCRDisk35DiskImage::shiftGcrHash() {
	gcrHash[2] &= 0xff;
	gcrHash[2] = (gcrHash[2] << 1) | (gcrHash[2] >> 7);
}

UINT8 GCRDisk35DiskImage::getGcrValue(UINT32 hashIndex, UINT8 value) {
	gcrHash[(hashIndex + 1) % 3] += value + (gcrHash[hashIndex] >> 8);
	gcrHash[hashIndex] &= 0xff;
	
	return (value ^ gcrHash[hashIndex]);
}

void GCRDisk35DiskImage::writeGcrTriplet(UINT8 value0,
										 UINT8 value1,
										 UINT8 value2,
										 bool isLastTriplet) {
	shiftGcrHash();
	value0 = getGcrValue(2, value0);
	value1 = getGcrValue(0, value1);
	value2 = getGcrValue(1, value2);
	
	writeGcrValue(((value0 & 0xc0) >> 2) |
				  ((value1 & 0xc0) >> 4) |
				  ((value2 & 0xc0) >> 6));
	writeGcrValue(value0 & 0x3f);
	writeGcrValue(value1 & 0x3f);
	if (!isLastTriplet)
		writeGcrValue(value2 & 0x3f);
}

void GCRDisk35DiskImage::writeGcrHash() {
	writeGcrValue(((gcrHash[0] & 0xc0) >> 2) |
				  ((gcrHash[1] & 0xc0) >> 4) |
				  ((gcrHash[2] & 0xc0) >> 6));
	writeGcrValue(gcrHash[0] & 0x3f);
	writeGcrValue(gcrHash[1] & 0x3f);
	writeGcrValue(gcrHash[2] & 0x3f);
}

void GCRDisk35DiskImage::writeAddressField(UINT32 diskImageTrackIndex,
										   UINT32 sectorIndex) {
	UINT32 gcrTrackIndex = (diskImageTrackIndex >> 1) & 0x3f;
	UINT32 gcrHeadIndex = (diskImageTrackIndex & 0x1) << 5 |
		(diskImageTrackIndex >> 7);
	
	writeNibble(0xd5);
	writeNibble(0xaa);
	writeNibble(0x96);
	
	initGcrChecksum();
	
	writeGcrValue(gcrTrackIndex);
	writeGcrValue(sectorIndex);
	writeGcrValue(gcrHeadIndex);
	writeGcrValue(0x22);
	
	writeGcrChecksum();
	
	writeNibble(0xde);
	writeNibble(0xaa);
}

void GCRDisk35DiskImage::writeDataField(UINT8 * sectorData) {
	writeNibble(0xd5);
	writeNibble(0xaa);
	writeNibble(0xad);
	
	initGcrHash();
	
	for (UINT32 index = 0; index < GCRDISK35_TAG_BYTENUM; index += 3)
		writeGcrTriplet(0, 0, 0, false);
	
	for (UINT32 index = 0; index < GCRDISK35_SECTOR_BYTENUM; index += 3)
		writeGcrTriplet(sectorData[index],
						sectorData[index + 1],
						sectorData[index + 2],
						false);
	writeGcrTriplet(sectorData[510],
					sectorData[511],
					0,
					true);
	
	writeGcrHash();
	
	writeNibble(0xde);
	writeNibble(0xaa);
}

void GCRDisk35DiskImage::writeTrack(UINT32 diskImageTrackIndex,
									UINT8 * diskImageTrackData) {
	writeSync(0x80, 10, 8);
	
	UINT32 sectorNum = getSectorNum(diskImageTrackIndex);
	for (UINT32 index = 0; index < sectorNum; index++) {
		if (index)
			writeSync(0x29, 10, 8);
		writeAddressField(diskImageTrackIndex, index);
		writeSync(0x08, 10, 9);
		writeDataField(&diskImageTrackData[GCRDISK35_SECTOR_BYTENUM * index]);
	}
}

bool GCRDisk35DiskImage::readAddressField(UINT32 diskImageTrackIndex,
										  UINT32 sectorIndex) {
	return false;
}

bool GCRDisk35DiskImage::readDataField(UINT8 * sectorData) {
	return false;
}

bool GCRDisk35DiskImage::readTrack(UINT32 diskImageTrackIndex,
								   UINT8 * diskImageTrackData) {
	return false;
}
